Skip to content

AVX2 kernel for float64 Log: 1.55x faster than NumPy - #96

Merged
tannevaled merged 1 commit into
mainfrom
perf/log64-avx2
Nov 7, 2026
Merged

tannevaled merged 1 commit into
mainfrom
perf/log64-avx2

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

float64 Log gets an AVX2 kernel and goes from 1.23× slower than NumPy to 1.55× faster on one Zen 3 core. This closes the last open defect in docs/perf.md's Exp/Log section.

Why it was slower: NumPy's 4.2 ns per element is glibc's scalar log, which is optimized-routines' log.c, the code Log ports. The gap was our scalar Go.

logAVX2 runs Log's table path on 4 doubles at a time, with Log's operations in its order.

  • Its one FMA, r = z·invc − 1, is VFMADD231PD. Every other product and sum is a separate VMULPD/VADDPD, which is how Go compiles them on amd64. The result is bit-identical to the Go loop.
  • invc and logc are gathered from the 16-byte table rows (VPGATHERQQ).
  • AVX2 has no 64-bit arithmetic shift and no int64→float64 conversion, so the exponent kd = int64(tmp)>>52 is built exactly: the float64 with bits 0x4330000000000000 | (u ^ 0x800), minus 2⁵² + 2048.
  • A block holding a lane that is not a positive normal, or one within 2⁻⁴ of 1 (Log's polynomial path), is handed back to the Go loop (logScalar) before anything is stored.

Tests:

  • TestLogAVX2MatchesGo holds it to the Go loop bit for bit: 300 000 inputs covering every exponent and table index, values near 1, the special values, every length 0–70, and in place.
  • Dropping the + kd·ln2lo term fails it, which TestLogULP alone would not catch (worst 0.5078 ULP, unchanged).
  • It is fenced, and the constant block's layout and the 16-byte logEntry are tested.
float64 Log, one Zen 3 core, load ≤ 3.7 16 Ki 1 Mi
v0.35.0 83.9 µs 5.06 ms
this PR 44.4 µs 2.59 ms
NumPy 2.5.4 np.log(a) 68.9–69.4 µs 4.33–4.41 ms

That is −47% and −49% (abbench, 8 interleaved rounds, p = 0.000), both sides allocating. The README now lists float64 Log under "faster". SECURITY.md has the audit entry.

🤖 Generated with Claude Code

float64 Log was 1.23x slower than NumPy at 16 Ki on Zen 3: NumPy calls
glibc's scalar log, the same optimized-routines log.c ours ports, so the
gap was our scalar Go.

logAVX2 runs Log's table path on four doubles at a time with Log's
operations in its order: its one FMA as VFMADD231PD, every other product
and sum as a separate VMULPD/VADDPD, as Go compiles them on amd64. invc
and logc are gathered (VPGATHERQQ); the exponent, for which AVX2 has no
64-bit arithmetic shift or int64 conversion, is built exactly with the
2^52 magic constant. Blocks with a non-positive-normal lane or a lane
within 2^-4 of 1 are handed back to the Go loop (logScalar).

TestLogAVX2MatchesGo holds it to the Go loop bit for bit; dropping the
kd*ln2lo term fails it (TestLogULP alone would not). Fenced; layout
tested. 16 Ki: 83.9 -> 44.4 us, 1 Mi: 5.06 -> 2.59 ms (abbench, 8 rounds,
p = 0.000); NumPy 69 us and 4.4 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 14d590b into main Nov 7, 2026
12 checks passed
@tannevaled
tannevaled deleted the perf/log64-avx2 branch November 7, 2026 20:49
tannevaled added a commit that referenced this pull request Nov 7, 2026
…xp, log (K1) (#97)

* kernels: import go-simd/floats for sum, elementwise, extrema, clip, exp, log (K1)

K1 of the go-scikit performance campaign (go-scikit/roadmap#27): ndarray now
imports go-simd/floats v0.9.0 and deletes its copies.

- exp, log, and the correctly rounded float32 exp/log (with the AVX2 kernels of
  #94/#96 and the NEON kernel of #95, now go-simd/floats#29): deleted here
  (Go sources, tests, generator kernels, assembly); Exp, Log, Exp32, Log32 and
  their P forms call floats.
- amd64 and arm64: the sum/sqrt/max/min/add/sub/mul/div kernels are floats';
  Clip is floats.ClampScalar. Max/Min resolve a zero result with the serial
  oracle (floats leaves the sign of zero unspecified).
- Sum is floats.SumPairwise on every architecture: NumPy's order, bit-identical
  to np.sum; serial at every size. A changed last bit is the fix; the golden
  values of TestSumMatchesNumPy are NumPy 2.5.4's.
- Not moved: the float32 elementwise/sqrt/extremum/AddBlocks8F32 kernels, the
  dot inside GEMM/Dot1D, and the ppc64le, loong64, riscv64 and s390x kernels
  (floats has none for them yet).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* kernels: test the parallel exp/log forms and Log10 (100% coverage gate)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant